package com.bugsnag.ohos;

import com.bugsnag.ohos.internal.ImmutableConfig;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * Capture and serialize the state of all threads at the time of an exception.
 *
 * @since 2021-07-01
 */
public class ThreadState implements JsonStream.Streamable {
    Throwable exc;
    boolean isUnhandled;
    ThreadSendPolicy sendThreads;
    Collection<String> projectPackages;
    Logger logger;
    java.lang.Thread currentThread;
    Map<java.lang.Thread, StackTraceElement[]> stackTraces;
    private List<Thread> threads;

    /**
     * constructor
     *
     * @param exc
     * @param isUnhandled
     * @param sendThreads
     * @param projectPackages
     * @param logger
     * @param currentThread
     * @param stackTraces
     */
    public ThreadState(Throwable exc, boolean isUnhandled, ThreadSendPolicy sendThreads,
                       Collection<String> projectPackages, Logger logger,
                       java.lang.Thread currentThread, Map<java.lang.Thread, StackTraceElement[]> stackTraces) {
        this.exc = exc;
        this.isUnhandled = isUnhandled;
        this.sendThreads = sendThreads;
        this.projectPackages = projectPackages;
        this.logger = logger;
        this.currentThread = currentThread;
        this.stackTraces = stackTraces;

        logger.d("new ThreadState()...");
        boolean recordThreads = sendThreads == ThreadSendPolicy.ALWAYS
            || (sendThreads == ThreadSendPolicy.UNHANDLED_ONLY && isUnhandled);

        Map<java.lang.Thread, StackTraceElement[]> stackTraces2 = stackTraces;
        if (stackTraces2 == null) {
            stackTraces2 = java.lang.Thread.getAllStackTraces();
        }
        java.lang.Thread currentThread2 = currentThread;
        if (currentThread2 == null) {
            currentThread2 = java.lang.Thread.currentThread();
        }
        if (recordThreads) {
            threads = captureThreadTrace(stackTraces2, currentThread2, exc, isUnhandled, projectPackages, logger);
        } else {
            threads = new ArrayList<>();
        }
        logger.d("new ThreadState() end...");
    }

    /**
     * constructor
     *
     * @param exc
     * @param isUnhandled
     * @param sendThreads
     * @param projectPackages
     * @param logger
     */
    public ThreadState(Throwable exc, boolean isUnhandled, ThreadSendPolicy sendThreads,
                       Collection<String> projectPackages, Logger logger) {
        this(exc, isUnhandled, sendThreads, projectPackages, logger, null, null);
    }

    /**
     * constructor
     *
     * @param exc
     * @param isUnhandled
     * @param config
     */
    public ThreadState(Throwable exc, boolean isUnhandled, ImmutableConfig config) {
        this(exc, isUnhandled, config.getSendThreads(), config.getProjectPackages(), config.getLogger());
    }

    public List<Thread> getThreads() {
        return threads;
    }

    private List<Thread> captureThreadTrace(
        Map<java.lang.Thread, StackTraceElement[]> traces, java.lang.Thread captureThread,
        Throwable throwable, boolean unhandled, Collection<String> packages, Logger log) {
        log.d("captureThreadTrace()...");
        if (!traces.containsKey(captureThread)) {
            traces.put(captureThread, captureThread.getStackTrace());
        }
        if (throwable != null && unhandled) { // unhandled errors use the exception trace for thread traces
            traces.put(captureThread, throwable.getStackTrace());
        }
        long currentThreadId = captureThread.getId();
        List<Thread> threadList = new ArrayList<>();
        for (java.lang.Thread thread : traces.keySet()) {
            StackTraceElement[] trace = traces.get(thread);
            Thread t;
            if (trace != null) {
                Stacktrace stacktrace = new Stacktrace(trace, packages, log);
                boolean errorThread = thread.getId() == currentThreadId;
                t = new Thread(thread.getId(), thread.getName(), ThreadType.OHOS, errorThread, stacktrace, log);
                threadList.add(t);
            }
        }
        return threadList;
    }

    @Override
    public void toStream(JsonStream writer) throws IOException {
        writer.beginArray();
        for (Thread thread : threads) {
            writer.value(thread);
        }
        writer.endArray();
    }
}
